package org.orbis.optimizer;

import java.util.Hashtable;
import java.util.TreeMap;
import java.util.Map.Entry;
import org.orbis.communicate.FlagsResult;
import org.orbis.constants.Constants;
import org.orbis.memoryblock.MemoryBlock;
import org.orbis.memoryblock.content.ContentBlock;
import org.orbis.memoryblock.content.OptimizerContentBlock;
import org.orbis.memoryblock.invert.InvertIndexBlock;
import org.orbis.memoryblock.invert.OptimizerInvertIndexBlock;
import org.orbis.memorycontroller.DiskController;
import org.orbis.memorycontroller.MemoryAllocator;
import org.orbis.memorycontroller.MemoryController;
import org.orbis.memorymanager.filter.DeleteFilter;
import org.orbis.structure.ContentInfo;
import org.orbis.structure.LookupPointer;

public class DeleteOptimizer extends Optimizer{
	private DeleteFilter filter;
	private MemoryController controller;
	private MemoryAllocator allocator;
	private DiskController disk;
	private ContentInfo contentInfo;

	public DeleteOptimizer(DeleteFilter filter, MemoryController controller, MemoryAllocator allocator,
		DiskController diskControl, ContentInfo contentInfo){
		this.filter = filter;
		this.controller = controller;
		this.allocator = allocator;
		this.disk = diskControl;
		this.contentInfo = contentInfo;
	}

	private Hashtable<LookupPointer, Integer> pickContent(int cluster, int subtype){
		Hashtable<LookupPointer, Integer> pointList = new Hashtable<LookupPointer, Integer>();
		LookupPointer currentPoint =
			allocator.getFirstPointer(cluster, Constants.MEMORY_TYPE_CONTENTS, subtype);

		OptimizerContentBlock block =
			new OptimizerContentBlock(disk.getBuffer(cluster, currentPoint.block()));
		while(currentPoint.point() != -1){
			// filter with every ID in the block
			int length = block.checkByFilter(filter, currentPoint.point());
			if(length > 0){
				pointList.put(currentPoint.withCluster(cluster), Integer.valueOf(length));
				currentPoint.modify(currentPoint.point(), block.getNextOne(currentPoint.point()));
			}

			// Find next block
			if(length <= 0 || currentPoint.point() == -1){
				if(block.getOption(ContentBlock.OPT_NEXT) == -1) break;
				else{
					currentPoint.modify(block.getOption(ContentBlock.OPT_NEXT), 0);
					block = new OptimizerContentBlock(disk.getBuffer(cluster, currentPoint.block()));
				}
			}
		}

		return pointList;
	}

	private void deleteContent(Hashtable<LookupPointer, Integer> pointList, int cluster, int subtype){
		OptimizerContentBlock contentBlock, nextContentBlock;
		int buffer = 0;
		LookupPointer prev, next;
		prev = allocator.getFirstPointer(cluster, Constants.MEMORY_TYPE_CONTENTS, subtype);
		contentBlock = new OptimizerContentBlock(disk.getBuffer(cluster, prev.block()));
		for(Entry<LookupPointer, Integer> point: pointList.entrySet()){
			next = point.getKey();
			if(prev.block() != next.block()){
				// Move from another block
				nextContentBlock = new OptimizerContentBlock(disk.getBuffer(cluster, next.block()));
				int neededSize = nextContentBlock.getAvailSize(buffer);
				byte[] val = nextContentBlock.getRawBody(0, neededSize);
				contentBlock.setRawBody(prev.point(), val);
				buffer = neededSize;
				contentBlock = nextContentBlock;
			}else{
				contentBlock.moveBody(prev.point(), next.point(), buffer);
				buffer += point.getValue().intValue();
			}
			prev = next;
		}
	}

	private TreeMap<LookupPointer, LookupPointer> deleteInvertIndex(int cluster){
		TreeMap<LookupPointer, LookupPointer> ptrList = new TreeMap<LookupPointer, LookupPointer>();
		MemoryBlock memBlock;
		OptimizerInvertIndexBlock invertBlock;

		int blockLength = allocator.blockLength();
		for(int i = 0; i < blockLength; i++){
			memBlock = allocator.dispense(cluster, i);
			if(!(memBlock instanceof InvertIndexBlock)) continue;

			invertBlock = new OptimizerInvertIndexBlock(memBlock.getBorrowedBody());
			LookupPointer ptr = LookupPointer.valueOf(i, 0).withCluster(cluster);
			while(!ptr.equals(LookupPointer.EMPTY)){
				FlagsResult[] results = invertBlock.pickIDbyFlag(filter, ptr.point());
				if(results == null) break;
				invertBlock.setBack(results, ptr.point());
				ptr = invertBlock.advancePointer(ptr);
			}
		}
		
		return ptrList;
	}

	@Override
	public void optimize(){
		// Stop indexing, suspend node
		controller.suspendMyNode();

		// Add block information to each block
		allocator.saveAllMemory();

		// 1. Delete content
		// Results are not taken
		// 2. Delete invert index
		// Result as: <Prev. lookup pointer, New lookup pointer>
		// 3. Change pointer from dictionary
		// 4. Reconstruct skip pointer

		for(int cluster = allocator.clusterID(0), i = 0; cluster != -1; i++, cluster = allocator.clusterID(i)){
			for(int j = 0; j < contentInfo.contentsNum(); j++){
				Hashtable<LookupPointer, Integer> pointList = pickContent(cluster, j);
				deleteContent(pointList, cluster, j);
			}

			deleteInvertIndex(cluster);
		}

		controller.resumeMyNode();
	}
}
